package cn.yunbamboos.mybatis.mapping.methods;

import cn.yunbamboos.mybatis.annotation.FieldStrategy;
import cn.yunbamboos.mybatis.mapping.sqlnode.CustomWhereSqlNode;
import cn.yunbamboos.mybatis.metadata.TableInfo;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.scripting.xmltags.DynamicSqlSource;
import org.apache.ibatis.scripting.xmltags.IfSqlNode;
import org.apache.ibatis.scripting.xmltags.MixedSqlNode;
import org.apache.ibatis.scripting.xmltags.SqlNode;
import org.apache.ibatis.scripting.xmltags.StaticTextSqlNode;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;


@SuppressWarnings("DuplicatedCode")
public class BaseDynamicCountMethod extends BaseDynamicQueryMethod {

    private static final Logger logger = LoggerFactory.getLogger(BaseMethod.class);

    public BaseDynamicCountMethod(String methodName, Configuration configuration, TableInfo tableInfo) {
        super(methodName, configuration, tableInfo);
    }

    @Override
    protected MappedStatement.Builder getMappedStatementBuilder() {
        Class<?> mapperType = tableInfo.getMapperType();
        MappedStatement.Builder builder = super.getMappedStatementBuilder();

        List<ResultMap> resultMaps = new ArrayList<>();
        ResultMap inlineResultMap = new ResultMap.Builder(configuration, mapperType.getName() + "-Inline", Integer.class,
                new ArrayList<>(), null).build();
        resultMaps.add(inlineResultMap);
        builder.resultMaps(resultMaps);
        return builder;
    }

    protected SqlSource createSqlSource() {
        List<SqlNode> contents = new ArrayList<>();
        // select * from table
        contents.add(new StaticTextSqlNode(this.getSQL()));
        // 添加动态条件
        getWhereSqlNode().ifPresent(contents::add);

        SqlNode sqlNode = new MixedSqlNode(contents);

        return new DynamicSqlSource(configuration, sqlNode);
    }

    protected Optional<SqlNode> getWhereSqlNode() {
        List<SqlNode> contents = new ArrayList<>();
        this.tableInfo.getPrimaryKeyList().forEach(pk -> {
            createSqlNode(pk.getPropertyType(), pk.getColumn(), pk.getFieldStrategy()).ifPresent(contents::add);
        });

        this.tableInfo.getFieldList().forEach(field -> {
            createSqlNode(field.getPropertyType(), field.getColumn(), field.getFieldStrategy()).ifPresent(contents::add);
        });

        this.tableInfo.getWhereList().forEach(each->{
            each.createSqlNode(configuration).ifPresent(contents::add);
        });

        SqlNode sqlNode = new MixedSqlNode(contents);
        return Optional.of(new CustomWhereSqlNode(configuration, sqlNode));
    }

    @Override
    protected String getSQL() {
        return "select count(1) from " + BACKTICK + this.tableInfo.getTableName() + BACKTICK;
    }

    private Optional<SqlNode> createSqlNode(Class<?> propertyType, String column, FieldStrategy strategy) {
        if (FieldStrategy.NEVER.equals(strategy)) {
            return Optional.empty();
        }
        if (String.class.equals(propertyType)) {
            return createSqlNodeString(column, strategy);
        } else {
            return createSqlNodeInt(column, strategy);
        }
    }

    private Optional<SqlNode> createSqlNodeInt(String column, FieldStrategy strategy) {
        String text = getText(column);
        String notNullTest = getNotNullTest(column);

        StaticTextSqlNode contents = new StaticTextSqlNode(text);
        if (FieldStrategy.ALWAYS.equals(strategy)) {
            return Optional.of(contents);
        } else if (FieldStrategy.NOT_NULL.equals(strategy)
                || FieldStrategy.NOT_EMPTY.equals(strategy)
                || FieldStrategy.DEFAULT.equals(strategy)) {
            return Optional.of(new IfSqlNode(contents, notNullTest));
        }
        logger.warn("int unknown column:{} strategy：{}", column, strategy);
        return Optional.empty();
    }

    private Optional<SqlNode> createSqlNodeString(String column, FieldStrategy strategy) {
        String text = getText(column);
        String notNullTest = getNotNullTest(column);
        String notEmptyTest = getNotEmptyTest(column);

        StaticTextSqlNode contents = new StaticTextSqlNode(text);
        if (FieldStrategy.ALWAYS.equals(strategy)) {
            return Optional.of(contents);
        } else if (FieldStrategy.NOT_NULL.equals(strategy)) {
            return Optional.of(new IfSqlNode(contents, notNullTest));
        } else if (FieldStrategy.NOT_EMPTY.equals(strategy) || FieldStrategy.DEFAULT.equals(strategy)) {
            return Optional.of(new IfSqlNode(contents, notNullTest + SPACE + AND + SPACE + notEmptyTest));
        }
        logger.warn("string unknown column:{} strategy：{}", column, strategy);
        return Optional.empty();
    }

    private String getText(String column) {
        // and `column` = #{conditions.column}
        return SPACE + AND + SPACE + BACKTICK + column + BACKTICK + SPACE + EQUALS + SPACE + HASH_LEFT_BRACE + CONDITIONS + DOT + column + RIGHT_BRACE;
    }

    private String getNotNullTest(String column) {
        return CONDITIONS + DOT + column + SPACE + EXCLAMATION_MARK + EQUALS + SPACE + NULL;
    }

    private String getNotEmptyTest(String column) {
        return CONDITIONS + DOT + column + SPACE + EXCLAMATION_MARK + EQUALS + SPACE + SINGLE_QUOTE + SINGLE_QUOTE;
    }
}
